Skip to main content

第34章内存分区模型

C++程序在运行时,内存会被划分为多个不同的区域,每个区域用于存储特定类型的数据,并具有不同的生命周期、管理方式等。不同编译器、操作系统细节略有差异,但核心分区模型统一。程序运行内存分为:代码区、全局区(静态区)、栈区、堆区,细分包含常量区(归属全局区)。

34.1 内存分区核心区分维度

各分区差异体现在三点:

  1. 存储内容:存放代码/全局变量/局部变量/动态内存等;
  2. 生命周期:程序全程/函数调用期间/手动分配释放;
  3. 管理主体:编译器自动管理 / 程序员手动new/delete。

34.2 代码区(Code Area)

34.2.1 特点

  1. 只读:防止程序运行时修改指令;
  2. 可共享:多个进程运行同一程序可共用一份代码,节省内存;
  3. 编译期确定大小,程序启动由操作系统加载进内存。

34.2.2 存储内容

所有函数二进制执行指令、编译器内置执行代码。 示例:

int add(int a,int b){ // add函数指令存储在代码区
return a+b;
}

34.3 全局区(静态区 Global Area)

34.3.1 特点

  1. 生命周期:程序启动分配,程序结束释放;
  2. 编译期分配空间;
  3. 未初始化全局/静态变量自动置0;
  4. 包含常量子区域(字符串常量、全局const常量,只读)。

34.3.2 存储内容

  1. 全局变量(所有函数外部定义);
  2. static修饰全局/局部静态变量;
  3. 字符串常量、全局const常量。 示例代码:
// 全局变量(全局区)
int g_a=10;
int g_b;// 未初始化,默认0
// 全局静态变量(全局区)
static int s_c=20;
// 全局常量(全局常量区)
const int c_d=30;

void func(){
// 局部静态变量,存全局区,非栈区
static int s_e = 200;
}

int main(){
// "test"字符串常量存储全局只读区
cout << "字符串常量地址:" << (void*)"test" << endl;
return 0;
}

34.4 栈区(Stack Area)

34.4.1 特点

  1. 自动管理:函数调用时分配,函数结束自动释放;
  2. 先进后出(FILO);
  3. 栈容量操作系统固定(通常几MB),超大局部数组、深层递归会栈溢出;
  4. 分配速度远快于堆。

34.2 存储内容

函数局部变量、函数形参、函数返回地址、局部临时对象。

注意:static局部变量不属于栈,存入全局区。 示例:

void print(int num){ // 参数num存栈区
int x=5; // 局部变量,栈区
string str="hello"; // str对象在栈,字符串数据在常量区
cout << num;
}

34.3 栈溢出常见场景

  1. 函数内定义超大局部数组:int arr[1000000];
  2. 递归调用层数过深,持续占用栈帧:
void recursive(){
int buf[1000];
recursive(); // 无限递归,栈溢出崩溃
}

34.5 堆区(Heap Area)

34.5.1 特点

  1. 手动管理:C++使用new分配、delete释放;
  2. 生命周期自主:分配后直到手动释放,否则程序结束系统回收;
  3. 空间大(GB级别),分配释放存在内存碎片;
  4. 分配效率低于栈。

34.5.2 存储内容

new / new[] 动态分配的变量、数组、对象。

int main(){
int* p1=new int(10); // 单个int存堆
int* p2=new int[5]; // 数组存堆
delete p1;
delete[] p2;
return 0;
}

34.6 堆与栈对比表

对比维度栈区堆区
管理者编译器自动程序员手动new/delete
生命周期函数调用周期分配到手动释放
空间大小固定小容量系统大内存
分配速度极快较慢
碎片问题频繁分配释放产生碎片
溢出风险深层递归、大数组栈溢出仅耗尽系统内存才溢出

34.7 内存生命周期总览

  1. 代码区、全局区:程序启动 → 程序结束全程存在;
  2. 栈区:函数进入分配,函数执行完毕自动销毁;
  3. 堆区:new分配开始,delete释放结束。

34.8 常见内存错误与分区关联

  1. 野指针:堆内存释放后指针未置空,继续访问;
int* p=new int(10);
delete p;
*p=20; // 野指针,未定义行为
  1. 内存泄漏:堆内存分配后忘记释放;
void leak(){
int* p=new int(100);
// 无delete,内存永久占用
}
  1. 栈溢出:超大局部数组、无限递归;
  2. 修改只读常量:字符串常量在全局只读区,修改直接崩溃
char* str="abc";
str[0]='H'; // 非法修改只读内存,程序崩溃